package room

import (
	"errors"
	"server/common"
	"time"

	"github.com/mitchellh/mapstructure"
)

type Worker struct {
	WorkerId  int    // Unique identifier for the worker
	ServiceId string // Node ID where the worker is hosted
}

type MediaService struct {
	ServiceId  string         // Unique identifier for the media service
	ConnId     string         // Connection ID for the node's WebSocket connection
	Workers    map[int]Worker // key: worker_id
	LastUpdate int64          // Timestamp of the last update
}

type MediaManager struct {
	services   map[string]MediaService // key: service_id
	dispatcher common.IMsgDispatcher   // Message dispatcher for handling media-related messages
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func NewMediaManager(dispatcher common.IMsgDispatcher) *MediaManager {
	return &MediaManager{
		services:   make(map[string]MediaService),
		dispatcher: dispatcher,
	}
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (s *MediaManager) RegisterService(data common.RegisterServiceReqData, connId string) error {
	// Check if the service already exists
	if service, exists := s.services[data.ServiceId]; exists {
		service.ConnId = connId
		service.LastUpdate = time.Now().Unix()
		for k := range service.Workers {
			delete(service.Workers, k)
		}
		for _, workerId := range data.Workers {
			if _, exists := service.Workers[workerId]; !exists {
				service.Workers[workerId] = Worker{WorkerId: workerId}
			}
		}
	} else {
		service := MediaService{
			ServiceId:  data.ServiceId,
			ConnId:     connId,
			Workers:    make(map[int]Worker),
			LastUpdate: time.Now().Unix(),
		}

		for _, workerId := range data.Workers {
			service.Workers[workerId] = Worker{WorkerId: workerId}
		}

		s.services[data.ServiceId] = service
	}

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (s *MediaManager) GetAvailableWorker(roomId string) *Worker {
	for _, service := range s.services {
		for _, worker := range service.Workers {
			// TODO: Check if the worker is available
			return &Worker{
				WorkerId:  worker.WorkerId,
				ServiceId: service.ServiceId,
			}
		}
	}
	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (s *MediaManager) GetServiceConnection(serviceId string) (string, error) {
	if service, exists := s.services[serviceId]; exists {
		return service.ConnId, nil
	}
	return "", errors.New("service not found")
}

// -------------------------------------------------------------------------------------------------
// TODO: 需要通知 room manager，media service 已经断开
// -------------------------------------------------------------------------------------------------
func (s *MediaManager) OnConnectionClosed(connId string) {
	LOG().WithField("connId", connId).Info(
		"connection closed, removing media service")

	for serviceId, service := range s.services {
		if service.ConnId == connId {
			delete(s.services, serviceId)
			LOG().WithField("serviceId", serviceId).Info(
				"removed media service due to connection closure")
			return
		}
	}
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (s *MediaManager) CreateRouter(rid string, roomId string) (*common.CreateRouterResData, error) {
	LOG(rid).WithField("roomId", roomId).Info("creating router for room")

	worker := s.GetAvailableWorker(roomId)
	if worker == nil {
		return nil, errors.New("no available worker to create router")
	}

	connId, err := s.GetServiceConnection(worker.ServiceId)
	if err != nil {
		LOG(rid).WithError(err).Error("failed to get service connection for worker", "workerId", worker.WorkerId)
		return nil, err
	}

	msg := common.Message{
		Cmd: common.CREATE_ROUTER_REQ,
		Rid: rid,
		Data: common.CreateRouterReqData{
			RoomId:    roomId,
			ServiceId: worker.ServiceId,
			WorkerId:  worker.WorkerId,
		},
	}

	res, err := s.dispatcher.DispatchSync(
		msg,
		common.ServiceInfo{
			ServiceType: common.SERVICE_TYPE_ROOM,
		},
		common.AccessServiceInfo,
		connId,
		common.CREATE_ROUTER_RES,
	)
	if err != nil {
		LOG(rid).WithError(err).Error("failed to dispatch create router request")
		return nil, err
	}

	var resData common.ResMsgData
	if err := mapstructure.Decode(res.Data, &resData); err != nil {
		LOG(rid).WithError(err).Error("failed to decode response data")
		return nil, err
	}

	var createRouterResData common.CreateRouterResData
	if err := mapstructure.Decode(resData.Data, &createRouterResData); err != nil {
		common.Logger(common.SERVICE_TYPE_ONLINE, rid).WithError(err).Error(
			"failed to decode create router response data")
		return nil, err
	}

	LOG(rid).WithField(
		"routerId", common.ToJson(createRouterResData)).Info("router created successfully")

	return &createRouterResData, nil
}
